#pragma once

#ifdef _MSC_VER
#include "windows.h"
#include <gl/gl.h>
#include <gl/glu.h>
#pragma comment(lib,"opengl32.lib")
#else
#include <GL/gl.h>
#endif

#include "Graphics/Graphics.hpp"
#include "Graphics/Vertex.hpp"
#include "Graphics/Color.hpp"
#ifdef DEBUG
#include <iostream>
#endif

namespace Graph
{

class OGLGraphics : public Graphics
{
    public:
        /**
         * Конструктор по умолчанию.
         */
        OGLGraphics();

        /**
         * � исует прямую линию между двумя точками.
         *
         * @param x1 начальная точка.
         * @param x2 конечная точка.
         */
        void drawLine(const Vertex &x1, const Vertex &x2);

        /**
         * � исует ломаную по массиву точек.
         *
         * @param vertices массив точек. Если точек < 1 ничего не будет нарисовано.
         */
        void drawPolyLine(const VertexVector &vertices);

        /**
         * Очищает сцену устанавливая фоновый цвет по умолчанию (черный).
         */
        void clear();

        /**
          * Устанавливает цвет, который будет установлен в буферах
          * после очистки. @see clear.
          *
          * @param color "фоновый" цвет, которым будет закрашена сцена
          *               после очистки.
          */
        void clearColor(const Color &color);

        /**
         * Сбрасывает содержимое буферов.
         * Необходимо вызвать после рисования всех объектов.
         */
        void flush();

        /**
         * � исует треугольник.
         *
         * @param a первая вершина треугольника.
         * @param b вторая вершина треугольника.
         * @param c третья вершина треугольника.
         */
        void drawTriangle(const Vertex &a, const Vertex &b, const Vertex &c);

        /**
         * � исует множество треугольников.
         * Каждые 3 вершины задают 1 треугольник.
         *
         * @param vertices массив вершин. Если количество вершин n не кратно 3м,
         *                 последние 3 % n вершин будут проигнорированы.
         *                 Если вершин меньше, чем 3 ничего не будет нарисовано.
         */
        void drawTriangles(const VertexVector &vertices);

        /**
         * � исует множетво треугольников, у каждого из которых
         * есть 2 общие вершины с соседними треугольниками.
         * Для первого треугольника используются вершины v0, v1, v2,
         * для второго v2, v1, v3, затем v2, v3, v4 и т.д.
         *
         * @param vertices массив вершин. Если вершин меньше, чем 3 ничего
         *                 не будет нарисовано.
         */
        void drawTriangleStrip(const VertexVector &vertices);

        /**
         * � исует множество треугольников, имеющих 1 общую вершину.
         * Для первого треугольника используются вершины v0, v1, v2,
         * для второго v0, v2, v3, затем v0 v3, v4 и т.д.
         *
         * @param vertices массив вершин. Если вершин меньше, чем 3 ничего
         *                 не будет нарисовано.
         */
        void drawTriangleFan(const VertexVector &vertices);

        /**
         * Отображает текст.
         *
         * @param text текст.
         * @param font шрифт.
         * @param size размер шрифта.
         * @param leftTop положение верхнего левого угла текста
         *                (в мировой системе координат).
         * @param direction направление текста.
         */
        void drawText(const string &text, const Font &font, const uint8_t size,
                                const Vertex &leftTop, const Direction &direction)
        {}

        /**
         * Задает область просмотра.
         * @see http://msdn.microsoft.com/en-us/library/windows/desktop/bb174469(v=vs.85).aspx
         * @see http://msdn.microsoft.com/en-us/library/dd374202(v=vs.85).aspx
         *
         * @param x левый верхний угол прямоугольника области просмотра в пикселях.
         * @param y левый верхний угол прямоугольника области просмотра в пикселях.
         * @param width ширина прямоугольника области просмотра в пикселях.
         * @param height высота прямоугольника области просмотра в пикселях.
         */
        void setViewport(int32_t x, int32_t y, int32_t width, int32_t height);

        /**
         * Применяет преобразование.
         * Текущая матрица преобразования умножается на матрицу
         * применяемого преобразования.
         *
         * @param matrix матрица применяемого преобразования.
         */
        void applyTransformation(const Matrix &matrix)
        {}

        /**
         * Устанавливает текущую матрицу преобразований в единичную.
         * Матрица, над котоьрой производятся операции определяется значением
         * MatrixMode.
         * @see setMatrixMode.
         */
        void resetTransformations()
        {}

        /**
         * Умножает текущую матрицу преобразований на матрицу масштабирования.
         *
         * @param scaleVertex вектор сдвига, компоненты которого определяют
         *                    изменение по соответствующей оси.
         */
        void scale(const Vertex &scaleVertex);

        /**
         * Умножает текущую матрицу преобразований на матрицу сдвига.
         *
         * @param moveVertex вектор сдвига, компоненты которого определяют
         *                   изменение по соответствующей оси.
         */
        void move(const Vertex &moveVertex);

        /**
         * Умножает текущую матрицу преобразований на матрицу поворота.
         *
         * @param axis вектор, вокруг которого совершается поворота.
         *             Не должен быть нулевым.
         * @param angle угол поворота.
         */
        void rotate(const Vertex &axis, const float angle);

        /**
         * Возвращает текущую матрицу преобразований,
         * определяемую значением MatrixMode.
         */
        const Matrix& getTransformation() const
        {
            //Matrix *a = new Matrix();
            Matrix *a = NULL;
            return *a;
        }

        /**
         * Помещает текущую матрицу преобразования в стек.
         */
        void pushTransformation()
        {}

        /**
         * Заменяет текущую матрицу преобразования матрицей
         * с вершины стека. Вершина стека удаляется из него.
         * Изначально в стеке всегда находится единичная матрица.
         * Стек не может быть пуст, при попытке удалить из стека
         * последнюю матрицу текущая матрица просто заменяется единичной.
         * Для каждой из матриц, определеямых MatrixMode
         * существует свой стек.
         *
         * @return новая текущая матрица преобразований.
         */
        const Matrix& popTransformation() 
        {
            //Matrix *a = new Matrix();
            Matrix *a = NULL;
            return *a;
        }

        /**
         * Возврашает вершину стека матриц преобразований.
         * Для каждой из матриц, определеямых MatrixMode
         * существует свой стек.
         *
         * @return матрица преобразования на вершине стека.
         */
        const Matrix& peekTransformation() const
        {
            //Matrix *a = new Matrix();
            Matrix *a = NULL;
            return *a;
        }

        /**
         * Возвращает идентификатор выбранной матрицы,
         * над которой будут производиться последующие операции.
         * По умолчанию MODELVIEW.
         *
         * @return идентификатор.
         */
        MatrixMode getMatrixMode() const;

        /**
         * Устанавливает матрицу, над которой будут производиться
         * последующие операции.
         *
         * @param matrixMode идентификатор матрицы преобразований.
         *                   Если значение не соответствует ни одному из
         *                   значений MatrixMode, ничего не будет сделано.
         */
        void setMatrixMode(const MatrixMode matrixMode);

        /**
          * Устанавливает единичную матрицу в качестве матрицы преобразрвания
          * для текущего значения MatrixMode.
          * @see setMatrixMode.
          */
        void loadIdentity();

        /**
          * Умножает текущую матрицу на матрицу параллельного проецирования.
          * (ортогональную матрицу).
          * Матрица определяется значением MatrixMode.
          * @see setMatrixMode.
          * @see http://opengl.gamedev.ru/doc/?func=glOrtho.
          *
          * @param xLeft Координата левой вертикальной плоскости отсечения.
          * @param xRight Координата правой вертикальной плоскости отсечения.
          * @param yBottom Координата нижней горизонтальной плоскости отсечения.
          * @param yTop Координата верхней горизонтальной плоскости отсечения.
          * @param zNear Координата ближней плоскости отсечения по глубине.
          * @param zFar Координата дальней плоскости отсечения по глубине.
          */
        void ortho(const float xLeft, const float xRight,
                           const float yBottom, const float yTop,
                           const float zNear, const float zFar);


        /**
         * Устанавливает толщину линии.
         * По умолчанию 1.
         *
         * @param lineWidth толщина линии.
         *
         * @deprecated толщина линии реализована с помощью костыля.
         */
        void setLineWidth(const float lineWidth);

        /**
         * Возвращает текущую толщину линии.
         *
         * @return толщина линии.
         *
         * @deprecated толщина линии реализована с помощью костыля.
         */
        float getLineWidth() const;

        /**
         * Возвращает 32х-битное представление цвета.
         * Цвет упаковывается в формате RGBA.
         *
         * @param r красная компонента цвета.
         * @param g зеленая компонента цвета.
         * @param b синяя компонента цвета.
         * @param a альфа компонента цвета.
         * @return 32х-битное представление цвета.
         */
        static uint32_t oglIntColor(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t a)
        {
            return (a << 24) | (b << 16) | (g << 8) | r;
        }

        /**
         * Возвращает 32х-битное представление цвета.
         * Цвет упаковывается в формате RGBA.
         *
         * @param r красная компонента цвета.
         * @param g зеленая компонента цвета.
         * @param b синяя компонента цвета.
         * @param a альфа компонента цвета.
         * @return 32х-битное представление цвета.
         */
        uint32_t intColor(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t a) const
        {
            return oglIntColor(r, g, b, a);
        }

        /**
         * Возвращает 32х-битное представление цвета.
         * Цвет упаковывается формате RGBA.
         *
         * @param color цвет для упаковки.
         * @return 32х-битное представление цвета.
         */
        uint32_t intColor(const Color &color) const
        {
            return intColor(color.getR(), color.getG(), color.getB(), color.getA());
        }

        /**
          * Возвращает цвет, соответствующий заданному 32х-битному представлению.
          *
          * @param color 32х-битное представление цвета. @see intColor.
          * @return соответсвующий объект типа Color.
          */
        Color fromIntColor(const uint32_t color) const
        {
            uint8_t r = (color >> 24) & 255;
            uint8_t g = (color >> 16) & 255;
            uint8_t b = (color >> 8) & 255;
            uint8_t a = color & 255;
            return Color(r, g, b, a);
        }

        /**
         * Деструктор.
         */
        ~OGLGraphics();

protected:

        /**
          * Отправляет на отрисовку массив вершин используя указанный режим.
          *
          * @param vertices массив вершин для рисования.
          * @param mode режим отрисовки примитивов, например, GL_LINE_STRP, GL_TRIANGLES и т.д.
          */
        void drawArrays(const VertexVector &vertices, const GLenum mode);
};

}

#if defined(_MSC_VER) && _MSC_VER >= 1600
#include <memory>
typedef std::shared_ptr<Graph::OGLGraphics> OGLGraphicsPtr;
#else
#include <tr1/memory>
typedef std::tr1::shared_ptr<Graph::OGLGraphics> OGLGraphicsPtr;
#endif
